package com.fary.beans.factory.support;

import com.fary.beans.BeanUtils;
import com.fary.core.SpringException;
import com.fary.beans.factory.DisposableBean;
import com.fary.beans.factory.config.BeanPostProcessor;
import com.fary.beans.factory.config.DestructionAwareBeanPostProcessor;
import com.fary.util.*;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {

    private static final String CLOSE_METHOD_NAME = "close";

    private static final String SHUTDOWN_METHOD_NAME = "shutdown";

    private static final Log logger = LogFactory.getLog(DisposableBeanAdapter.class);

    private final Object bean;

    private final String beanName;

    private final boolean invokeDisposableBean;

    private final boolean nonPublicAccessAllowed;

    private String destroyMethodName;

    private transient Method destroyMethod;

    private List<DestructionAwareBeanPostProcessor> beanPostProcessors;


    /**
     * Create a new DisposableBeanAdapter for the given bean.
     *
     * @param bean           the bean instance (never {@code null})
     * @param beanName       the name of the bean
     * @param beanDefinition the merged bean definition
     * @param postProcessors the List of BeanPostProcessors
     *                       (potentially DestructionAwareBeanPostProcessor), if any
     */
    public DisposableBeanAdapter(Object bean, String beanName, RootBeanDefinition beanDefinition,
                                 List<BeanPostProcessor> postProcessors) {

        Assert.notNull(bean, "Disposable bean must not be null");
        this.bean = bean;
        this.beanName = beanName;
        // 1.判断bean是否需要调用DisposableBean的destroy方法
        this.invokeDisposableBean = (this.bean instanceof DisposableBean && !beanDefinition.isExternallyManagedDestroyMethod("destroy"));
        this.nonPublicAccessAllowed = beanDefinition.isNonPublicAccessAllowed();
        String destroyMethodName = inferDestroyMethodIfNecessary(bean, beanDefinition);
        if (destroyMethodName != null && !(this.invokeDisposableBean && "destroy".equals(destroyMethodName)) &&
                !beanDefinition.isExternallyManagedDestroyMethod(destroyMethodName)) {
            // 2.拿到自定义的destroy方法名
            this.destroyMethodName = destroyMethodName;
            // 3.拿到自定义的destroy方法，赋值给this.destroyMethod
            this.destroyMethod = determineDestroyMethod(destroyMethodName);
            if (this.destroyMethod == null) {
                if (beanDefinition.isEnforceDestroyMethod()) {
                    // 4.如果destroy方法名为空，并且enforceDestroyMethod为true，则抛出异常
                    throw new SpringException("Could not find a destroy method named '" + destroyMethodName + "' on bean with name '" + beanName + "'");
                }
            } else {
                // 5.拿到destroy方法的参数类型数组
                Class<?>[] paramTypes = this.destroyMethod.getParameterTypes();
                if (paramTypes.length > 1) {
                    // 6.如果destroy方法的参数大于1个，则抛出异常
                    throw new SpringException("Method '" + destroyMethodName + "' of bean '" +
                            beanName + "' has more than one parameter - not supported as destroy method");
                } else if (paramTypes.length == 1 && boolean.class != paramTypes[0]) {
                    // 7.如果destroy方法的参数为1个，并且该参数的类型不为boolean，则抛出异常
                    throw new SpringException("Method '" + destroyMethodName + "' of bean '" +
                            beanName + "' has a non-boolean parameter - not supported as destroy method");
                }
            }
        }
        // 8.查找DestructionAwareBeanPostProcessors，并赋值给this.beanPostProcessors
        this.beanPostProcessors = filterPostProcessors(postProcessors, bean);
    }

    /**
     * Create a new DisposableBeanAdapter for the given bean.
     *
     * @param bean           the bean instance (never {@code null})
     * @param postProcessors the List of BeanPostProcessors
     *                       (potentially DestructionAwareBeanPostProcessor), if any
     */
    public DisposableBeanAdapter(Object bean, List<BeanPostProcessor> postProcessors) {
        Assert.notNull(bean, "Disposable bean must not be null");
        this.bean = bean;
        this.beanName = bean.getClass().getName();
        this.invokeDisposableBean = (this.bean instanceof DisposableBean);
        this.nonPublicAccessAllowed = true;
        this.beanPostProcessors = filterPostProcessors(postProcessors, bean);
    }

    /**
     * Create a new DisposableBeanAdapter for the given bean.
     */
    private DisposableBeanAdapter(Object bean, String beanName, boolean invokeDisposableBean,
                                  boolean nonPublicAccessAllowed, String destroyMethodName,
                                  List<DestructionAwareBeanPostProcessor> postProcessors) {

        this.bean = bean;
        this.beanName = beanName;
        this.invokeDisposableBean = invokeDisposableBean;
        this.nonPublicAccessAllowed = nonPublicAccessAllowed;
        this.destroyMethodName = destroyMethodName;
        this.beanPostProcessors = postProcessors;
    }


    /**
     * If the current value of the given beanDefinition's "destroyMethodName" property is
     * {@link AbstractBeanDefinition#INFER_METHOD}, then attempt to infer a destroy method.
     * Candidate methods are currently limited to public, no-arg methods named "close" or
     * "shutdown" (whether declared locally or inherited). The given BeanDefinition's
     * "destroyMethodName" is updated to be null if no such method is found, otherwise set
     * to the name of the inferred method. This constant serves as the default for the
     * {@code @Bean#destroyMethod} attribute and the value of the constant may also be
     * used in XML within the {@code <bean destroy-method="">} or {@code
     * <beans default-destroy-method="">} attributes.
     * <p>Also processes the {@link java.io.Closeable} and {@link java.lang.AutoCloseable}
     * interfaces, reflectively calling the "close" method on implementing beans as well.
     */
    private String inferDestroyMethodIfNecessary(Object bean, RootBeanDefinition beanDefinition) {
        // 1.拿到beanDefinition的destroy方法名
        String destroyMethodName = beanDefinition.getDestroyMethodName();
        // 2.如果destroy方法名为“(inferred)”|| destroyMethodName为null，并且bean是AutoCloseable实例
        if (AbstractBeanDefinition.INFER_METHOD.equals(destroyMethodName) || (destroyMethodName == null && bean instanceof AutoCloseable)) {
            // Only perform destroy method inference or Closeable detection
            // in case of the bean not explicitly implementing DisposableBean
            // 3.如果bean没有实现DisposableBean接口，则尝试推测destroy方法的名字
            if (!(bean instanceof DisposableBean)) {
                try {
                    // 4.尝试在bean中寻找方法名为close的方法作为destroy方法
                    return bean.getClass().getMethod(CLOSE_METHOD_NAME).getName();
                } catch (NoSuchMethodException ex) {
                    try {
                        // 5.尝试在bean中寻找方法名为close的方法作为shutdown方法
                        return bean.getClass().getMethod(SHUTDOWN_METHOD_NAME).getName();
                    } catch (NoSuchMethodException ex2) {
                        // no candidate destroy method found
                    }
                }
            }
            return null;
        }
        return (StringUtils.hasLength(destroyMethodName) ? destroyMethodName : null);
    }

    /**
     * Search for all DestructionAwareBeanPostProcessors in the List.
     *
     * @param processors the List to search
     * @return the filtered List of DestructionAwareBeanPostProcessors
     */
    private List<DestructionAwareBeanPostProcessor> filterPostProcessors(List<BeanPostProcessor> processors, Object bean) {
        List<DestructionAwareBeanPostProcessor> filteredPostProcessors = null;
        if (!CollectionUtils.isEmpty(processors)) {
            filteredPostProcessors = new ArrayList<>(processors.size());
            // 1.遍历所有的BeanPostProcessor
            for (BeanPostProcessor processor : processors) {
                // 2.如果processor是DestructionAwareBeanPostProcessor
                if (processor instanceof DestructionAwareBeanPostProcessor) {
                    DestructionAwareBeanPostProcessor dabpp = (DestructionAwareBeanPostProcessor) processor;
                    if (dabpp.requiresDestruction(bean)) {
                        // 3.如果给定的bean实例需要通过此后处理器进行销毁，则添加到filteredPostProcessors
                        filteredPostProcessors.add(dabpp);
                    }
                }
            }
        }
        return filteredPostProcessors;
    }


    @Override
    public void run() {
        destroy();
    }

    @Override
    public void destroy() {
        if (!CollectionUtils.isEmpty(this.beanPostProcessors)) {
            for (DestructionAwareBeanPostProcessor processor : this.beanPostProcessors) {
                processor.postProcessBeforeDestruction(this.bean, this.beanName);
            }
        }

        if (this.invokeDisposableBean) {
            if (logger.isTraceEnabled()) {
                logger.trace("Invoking destroy() on bean with name '" + this.beanName + "'");
            }
            try {
                ((DisposableBean) this.bean).destroy();
            } catch (Throwable ex) {
                String msg = "Invocation of destroy method failed on bean with name '" + this.beanName + "'";
                if (logger.isDebugEnabled()) {
                    logger.info(msg, ex);
                } else {
                    logger.info(msg + ": " + ex);
                }
            }
        }

        if (this.destroyMethod != null) {
            invokeCustomDestroyMethod(this.destroyMethod);
        } else if (this.destroyMethodName != null) {
            Method methodToCall = determineDestroyMethod(this.destroyMethodName);
            if (methodToCall != null) {
                invokeCustomDestroyMethod(methodToCall);
            }
        }
    }


    private Method determineDestroyMethod(String name) {
        try {
            return findDestroyMethod(name);
        } catch (IllegalArgumentException ex) {
            throw new SpringException("Could not find unique destroy method on bean with name '" +
                    this.beanName + ": " + ex.getMessage());
        }
    }

    private Method findDestroyMethod(String name) {
        return (this.nonPublicAccessAllowed ?
                BeanUtils.findMethodWithMinimalParameters(this.bean.getClass(), name) :
                BeanUtils.findMethodWithMinimalParameters(this.bean.getClass().getMethods(), name));
    }

    /**
     * Invoke the specified custom destroy method on the given bean.
     * <p>This implementation invokes a no-arg method if found, else checking
     * for a method with a single boolean argument (passing in "true",
     * assuming a "force" parameter), else logging an error.
     */
    private void invokeCustomDestroyMethod(final Method destroyMethod) {
        Class<?>[] paramTypes = destroyMethod.getParameterTypes();
        final Object[] args = new Object[paramTypes.length];
        if (paramTypes.length == 1) {
            args[0] = Boolean.TRUE;
        }
        if (logger.isTraceEnabled()) {
            logger.trace("Invoking destroy method '" + this.destroyMethodName +
                    "' on bean with name '" + this.beanName + "'");
        }
        try {
            ReflectionUtils.makeAccessible(destroyMethod);
            destroyMethod.invoke(this.bean, args);
        } catch (InvocationTargetException ex) {
            String msg = "Destroy method '" + this.destroyMethodName + "' on bean with name '" +
                    this.beanName + "' threw an exception";
            if (logger.isDebugEnabled()) {
                logger.info(msg, ex.getTargetException());
            } else {
                logger.info(msg + ": " + ex.getTargetException());
            }
        } catch (Throwable ex) {
            logger.info("Failed to invoke destroy method '" + this.destroyMethodName +
                    "' on bean with name '" + this.beanName + "'", ex);
        }
    }


    /**
     * Serializes a copy of the state of this class,
     * filtering out non-serializable BeanPostProcessors.
     */
    protected Object writeReplace() {
        List<DestructionAwareBeanPostProcessor> serializablePostProcessors = null;
        if (this.beanPostProcessors != null) {
            serializablePostProcessors = new ArrayList<>();
            for (DestructionAwareBeanPostProcessor postProcessor : this.beanPostProcessors) {
                if (postProcessor instanceof Serializable) {
                    serializablePostProcessors.add(postProcessor);
                }
            }
        }
        return new DisposableBeanAdapter(this.bean, this.beanName, this.invokeDisposableBean,
                this.nonPublicAccessAllowed, this.destroyMethodName, serializablePostProcessors);
    }


    /**
     * Check whether the given bean has any kind of destroy method to call.
     *
     * @param bean           the bean instance
     * @param beanDefinition the corresponding bean definition
     */
    public static boolean hasDestroyMethod(Object bean, RootBeanDefinition beanDefinition) {
        // 1.如果bean实现了DisposableBean接口 或者 bean是AutoCloseable实例，则返回true
        if (bean instanceof DisposableBean || bean instanceof AutoCloseable) {
            return true;
        }
        // 2.拿到bean自定义的destroy方法名
        String destroyMethodName = beanDefinition.getDestroyMethodName();
        // 3.如果自定义的destroy方法名为“(inferred)”（该名字代表需要我们自己去推测destroy的方法名），
        // 则检查该bean是否存在方法名为“close”或“shutdown”的方法，如果存在，则返回true
        if (AbstractBeanDefinition.INFER_METHOD.equals(destroyMethodName)) {
            return (ClassUtils.hasMethod(bean.getClass(), CLOSE_METHOD_NAME) ||
                    ClassUtils.hasMethod(bean.getClass(), SHUTDOWN_METHOD_NAME));
        }
        // 4.如果destroyMethodName不为空，则返回true
        return StringUtils.hasLength(destroyMethodName);
    }

    /**
     * Check whether the given bean has destruction-aware post-processors applying to it.
     *
     * @param bean           the bean instance
     * @param postProcessors the post-processor candidates
     */
    public static boolean hasApplicableProcessors(Object bean, List<BeanPostProcessor> postProcessors) {
        if (!CollectionUtils.isEmpty(postProcessors)) {
            // 1.遍历所有的BeanPostProcessor
            for (BeanPostProcessor processor : postProcessors) {
                // 2.如果processor是DestructionAwareBeanPostProcessor
                if (processor instanceof DestructionAwareBeanPostProcessor) {
                    DestructionAwareBeanPostProcessor dabpp = (DestructionAwareBeanPostProcessor) processor;
                    if (dabpp.requiresDestruction(bean)) {
                        // 3.如果给定的bean实例需要通过此后处理器进行销毁，则返回true
                        return true;
                    }
                }
            }
        }
        return false;
    }

}